Skip to content

feat(web): review cleanup β€” per-area feature flags, ASPM off by default - #925

Merged
arielshad merged 16 commits into
mainfrom
feat/133-review-cleanup-cuts
Oct 11, 2026
Merged

arielshad merged 16 commits into
mainfrom
feat/133-review-cleanup-cuts

Conversation

@arielshad

@arielshad arielshad commented Oct 9, 2026 •

Copy link
Copy Markdown
Contributor

What

This is the cleanup PR from the T3 Code product review. It covers the items the owner agreed to, with one commit per item:

Commit Item Change
bb81a67 C1 The stale good-first-issue and monthly recap watchers no longer run in users' daemons (_serve, shep ui, dev:web). They now run from the new scheduled workflow .github/workflows/contributor-maintenance.yml, through two new commands, shep contributors stale-issues and shep contributors recap. Both reuse the existing use cases. The watcher services are deleted. The contributor view (leaderboard, contributor doctor) moves to /contributors, which is linked from CONTRIBUTING.md and not from the sidebar. /onboarding keeps only the collaboration tutorial and leaves the sidebar.
2810b71 C3 featureFlags.aspm now defaults to false in TypeSpec, the defaults factory and the web env fallback. Values users already saved are kept; no migration touches them. ROADMAP.md is updated to match. The e2e tests that visit /aspm turn the flag on for the test and back off afterwards.
0b9221f C4 Supply-chain security is folded into ASPM, because folding was cheap and removing it was not. Its own supplyChainSecurity flag is gone, and every surface follows aspm through isSupplyChainSecurityEnabled(): the feature-agent pre-check, the canvas badge, the Settings section and shep security enforce. SHEP_SUPPLY_CHAIN_SECURITY still overrides the flag in either direction. Shep's own security-enforce CI job already sets it to true, so the release gate keeps running. The feature_flag_supply_chain_security column stays and is no longer read.
d3a2157 C7 Removes system.autoUpdate and the placeholder agent types aider and continue. The sys_auto_update column is NOT NULL with no default, so it is still written as 1 but never read. A settings row that still holds aider or continue reads back as the default agent. agentQuestionBridge, AgentQuestionExecutorBridge and InteractionBubble are untouched.
ef9c9ae C2 Adds 12 software-factory flags, all on by default: spaces, trackers, knowledge, signals, opportunities, feedback, discovery, incidents, outcomes, docsFirst, autopilot, factory. They are stored in migration 169 (DEFAULT 1). Each flag gates its area's nav entry, pages, intake API, CLI group, daemon loop and, for docsFirst, the docs gate. A new view at /settings/feature-flags lists every flag with a one-line description, its default and an on/off switch; Settings links to it. shep settings flags [enable|disable <flag>] does the same from the CLI.
1b681a2 C5 Deletes the Vercel, Netlify, AWS Amplify and GCP Cloud Run stub providers, their enum members and all "Coming soon" UI. Cloudflare Pages is unchanged. An application that saved a removed provider reads it back as "no provider selected".

b4e7420 renumbers the spec to 135: the telemetry and unified-decisions workstreams took 133 and 134.

Why

Implements specs/135-review-cleanup-cuts/, from the "Where Shep Is Today and What to Cut" section of docs/competitors/t3code.md.

Screenshots / Recording

I checked the feature-flags view at desktop and phone width in the running app with pnpm dev:web and Playwright. Turning spaces off removed the Spaces link from the sidebar and made /spaces return 404; turning it back on restored both. (The screenshots are local to the session; I can attach them if needed.)

Testing

Local runs, on the merge of main at d97bc6f (telemetry #923, decisions #922/#924):

Check Result
pnpm generate committed output matches what it generates
pnpm tsp:compile no warnings
pnpm lint, pnpm format:check pass
pnpm typecheck pass
pnpm test:unit 14,927 passed
pnpm test:int 2,139 passed
pnpm check:stories pass
pnpm build:release pass
pnpm build:storybook pass
Web e2e (CI settings, a flaky test counts as a failure) 86 of 86 passed

New tests:

  • feature-flag catalog completeness
  • List/SetFeatureFlag use cases
  • the CLI gate helper
  • shep settings flags
  • the background-sync gating
  • requireFeaturePage
  • feedback and alerts returning 404 while their flag is off
  • the docs-first gate
  • migration 169
  • a repository round-trip of every new flag
  • mapper fallbacks for the removed agent and provider values
  • the new contributor commands

Stories added: FeatureFlagsList, FeatureFlagsPageClient, ProviderList, CloudProviderIcons. SettingsRows (and its stories) now come from main; this PR keeps only the row's flex basis so switches stay beside long descriptions.

Notes for the reviewer

  • Recap data in the workflow: the recap use case reads recognition events from the local database. In Actions that database starts empty, which matches what the committed recaps/ files already show (zero events), since recognition is still recorded by hand (see CONTRIBUTING). The stale-issues job reads GitHub and works as is.
  • Migration number: 169. main holds 166 (agent-question decisions), 167 (decision timeout) and 168 (telemetry).
  • i18n e2e: the language spec waits for the language save by its request body and no longer on response.finished(), which hung for 60s in CI while the re-rendered page streamed.
  • Windows test harness: the git-config isolation now uses main's empty-config-file approach; the pty teardown tolerates a lingering ConPTY host (4d1b86f).
  • CLI name: shep security enforce keeps its name. Moving it under shep aspm would have broken existing CI scripts.

Checklist

  • pnpm lint passes
  • pnpm format:check passes
  • pnpm typecheck passes
  • pnpm test:unit and pnpm test:int pass
  • pnpm build succeeds
  • pnpm build:storybook succeeds and every new component has a colocated .stories.tsx
  • pnpm tsp:compile ran and output.ts is committed
  • Tests landed RED-first
  • No domain/ or application/ file imports from infrastructure/
  • Conventional Commits; feat/fix for user-visible changes
  • LESSONS.md updated: the flag checklist points at the catalog, plus notes on agent worktrees, Windows git-config isolation and waiting for a specific server action in e2e

πŸ€– Generated with Claude Code

https://claude.ai/code/session_01TxtN2VcWzg6NTCLj4DbmfL

claude and others added 8 commits October 9, 2026 09:38
The stale good-first-issue and monthly recap watchers (spec 097) ran inside
every user's daemon. They are Shep-maintainer automation, so they now run
from .github/workflows/contributor-maintenance.yml through two new
subcommands, `shep contributors stale-issues` and `shep contributors recap`,
which reuse the existing use cases. The watcher services are deleted.

The contributor view (leaderboard, contributor doctor) moves from
/onboarding to /contributors, linked from CONTRIBUTING.md and not from the
sidebar. /onboarding keeps only the collaboration tutorial that the
supervisor and agents pages link to, and leaves the sidebar.

Claude-Session: https://claude.ai/code/session_01TxtN2VcWzg6NTCLj4DbmfL

Co-Authored-By: Shep Bot <shep-agent@users.noreply.github.com>
ROADMAP said ASPM ships behind a flag, but the flag defaulted to on.
ASPM is a separate product category, so new installs now start with
featureFlags.aspm = false (TypeSpec default, defaults factory and the web
env fallback). Values users already persisted are left as they are: no
migration touches feature_flag_aspm.

The e2e suites that visit /aspm turn the flag on from Settings for the
test and restore it afterwards.

Claude-Session: https://claude.ai/code/session_01TxtN2VcWzg6NTCLj4DbmfL

Co-Authored-By: Shep Bot <shep-agent@users.noreply.github.com>
Supply-chain security had its own `supplyChainSecurity` flag and overlapped
ASPM. It now has no flag of its own: isSupplyChainSecurityEnabled() derives
it from featureFlags.aspm, and the feature-agent pre-check, the canvas
security badge, the Settings section and `shep security enforce` all use
it. SHEP_SUPPLY_CHAIN_SECURITY still overrides it for CI: "false" turns it
off and "true" turns it on, which Shep's own security-enforce job already
sets, so the release gate keeps running on a fresh install.

The field leaves TypeSpec, the defaults, the mapper and the repository SQL.
The feature_flag_supply_chain_security column stays (NOT NULL DEFAULT 1)
so older builds keep reading it; no data is dropped.

Claude-Session: https://claude.ai/code/session_01TxtN2VcWzg6NTCLj4DbmfL

Co-Authored-By: Shep Bot <shep-agent@users.noreply.github.com>
Nothing read `system.autoUpdate`, so it leaves TypeSpec, the defaults and
the mapper. Its sys_auto_update column is NOT NULL with no default, so the
mapper keeps writing 1 (the old default) and never reads it; older builds
still see their usual value.

The Aider and Continue agent types were "coming soon" placeholders with no
executor. They leave the AgentType enum, the agent catalog, the canvas
icons, the TUI picker translations and the stories. A settings row that
still holds either value reads back as the default agent, so no data is
rewritten.

Claude-Session: https://claude.ai/code/session_01TxtN2VcWzg6NTCLj4DbmfL

Co-Authored-By: Shep Bot <shep-agent@users.noreply.github.com>
Specs 120–132 shipped with no flag. Each software-factory area now has one,
default on so nothing changes for users: spaces, trackers, knowledge,
signals, opportunities, feedback, discovery, incidents, outcomes, docsFirst,
autopilot and factory (TypeSpec, migration 166 with DEFAULT 1, mapper,
repository SQL, defaults, web flag state).

Each flag gates its area: sidebar entries, pages (requireFeaturePage),
POST /api/feedback and /api/alerts (404 while off), CLI groups
(gateByFeatureFlag hides the group and says how to turn it on; shep aspm
now uses it too), the daemon's sync, discovery, outcome and autopilot
passes (checked on every pass), and the docs-first planning instructions
and merge gate.

A domain catalog gives every flag a group and a one-line description.
ListFeatureFlagsUseCase and SetFeatureFlagUseCase serve both
/settings/feature-flags (every flag, its default and a switch; linked from
Settings) and `shep settings flags [enable|disable <flag>]`. The Settings
page's Feature Flags section renders the same catalog-driven list instead
of hand-written rows, which also adds the missing githubImport switch.

Claude-Session: https://claude.ai/code/session_01TxtN2VcWzg6NTCLj4DbmfL

Co-Authored-By: Shep Bot <shep-agent@users.noreply.github.com>
Cloudflare Pages was the only cloud deploy provider that worked. Vercel, Netlify,
AWS Amplify and GCP Cloud Run were stubs listed as "Coming soon" that threw on
connect and deploy. This removes them. Cloudflare Pages works as before.

- TypeSpec: CloudDeploymentProvider keeps only CloudflarePages (output.ts and
  apis/json-schema regenerated)
- core: delete the four *.provider.stub.ts files, base-provider-stub.ts, their DI
  registrations and ProviderNotImplementedError; drop `enabled` from
  ICloudDeploymentProvider, the registry descriptor and ListCloudProvidersUseCase
- the registry now lists only ids that have an adapter, and resolves them from the
  container it was registered in rather than the global root
- add domain parseCloudDeploymentProvider, replacing four copies of parseProvider
  in the CLI commands and web routes
- CLI: `shep app cloud-providers ls/connect` lose the coming-soon output
- web: drop the "Coming soon" rows, disabled states and removed brand icons from
  ProviderList, ProviderDropdown, DeployPanel, DeployButton, SmartDeployCluster
  and ConnectProviderModal; share labels and the list entry type in
  cloud-providers.ts; update stories, add ProviderList and CloudProviderIcons
  stories

Persisted values: connect rejected the stubs, so no cloud_provider_tokens row
can hold one, but `select-provider` and `shep app deploy start --provider` could
store a stub id in applications.cloud_deployment_provider. No migration rewrites
data. The application mapper reads an unknown provider id as no selection, so
deploy falls back to the first connected provider. Token listing skips unknown
ids too.

Claude-Session: https://claude.ai/code/session_01TxtN2VcWzg6NTCLj4DbmfL

Co-Authored-By: Shep Bot <shep-agent@users.noreply.github.com>
Co-Authored-By: Shep Bot <shep-agent@users.noreply.github.com>
@arielshad arielshad changed the title Review cleanup: move maintainer automation off users' machines, ASPM off by default, per-area flags feat(web): review cleanup β€” per-area feature flags, ASPM off by default Oct 9, 2026
claude and others added 3 commits October 9, 2026 11:41
Specs 133 and 134 are taken by the telemetry and unified-decisions workstreams.
Comments and test names that cite the spec follow the new number.

Co-Authored-By: Shep Bot <shep-agent@users.noreply.github.com>
The view's group headings jumped from h1 to h3; they are h2 on the page and stay
h3 inside the Settings section. The phone navigation test opens the ASPM Security
group, so it turns the aspm flag on first now that it defaults to off.

Co-Authored-By: Shep Bot <shep-agent@users.noreply.github.com>
git 2.56 on the Windows runner rejects GIT_CONFIG_GLOBAL=NUL, so every harness
real-git test failed in git init. Git for Windows maps /dev/null to its null device,
as the merge-step real-git tests already rely on.

Co-Authored-By: Shep Bot <shep-agent@users.noreply.github.com>

Copy link
Copy Markdown
Contributor Author

Unit Tests (windows-latest) failed on 8c0e469 in tests/integration/infrastructure/services/harness/tools/builtin-tools.test.ts. Every git init in the harness test helper failed with fatal: unable to access 'NUL': Invalid argument.

The cause is not this PR's code. The Windows runner now has git 2.56.0, which rejects GIT_CONFIG_GLOBAL=NUL, and tests/helpers/harness/temp-git-repo.ts sets exactly that. The last CI run on main (Oct 5) passed on an older git, but main will hit the same failure on the new runner image.

9264d3c fixes it here by using /dev/null on every platform. Git for Windows maps that path to its null device, and the merge-step real-git tests already use it and pass on this same runner. The harness integration tests pass locally on Linux; Windows CI will confirm.


Generated by Claude Code

Copy link
Copy Markdown
Contributor Author

Unit Tests (windows-latest) failed on 9264d3c in one test only: pty-terminal-session.service.test.ts β€Ί "creates terminal successfully with valid working directory". It failed with EBUSY: resource busy or locked, rmdir …\shep-test-3qKjXS during teardown, after its assertions had passed. The other 14,622 tests passed, and the harness git fix from the previous comment worked.

I don't think this PR causes it. The PR does not touch the terminal service or that test, and the same unit step passed on Windows on the previous commit (8c0e469). The test already waits for the shell to exit and then retries rmdir for about 3 s (removeDirWithRetry). The runner log ends with "Terminate orphan process: pid (1732) (conhost)", so the ConPTY host was still holding the temp directory after that window.

I tried to re-run the failed job and got a 403, because this integration cannot re-run workflows. Could someone with access re-run "Unit Tests (windows-latest)"?

If it keeps happening, my proposed fix (not applied here, to keep this PR's scope) is to make that test's teardown tolerate a lingering ConPTY handle on Windows. The temp directory lives under the OS temp folder and does not affect any assertion:

} finally {
  try {
    removeDirWithRetry(tempDir);
  } catch (error) {
    // Windows: conhost can outlive the shell and keep tempDir open.
    if (process.platform !== 'win32' || (error as NodeJS.ErrnoException).code !== 'EBUSY') throw error;
  }
}

Generated by Claude Code

On Windows the conhost.exe that node-pty starts with the shell inherits its cwd and
can outlive the shell's exit event, so the pty test's teardown failed with EBUSY even
after waiting for exit and retrying. The test now leaves that temp dir to the OS on
that one Windows error; any other error still fails it.

Co-Authored-By: Shep Bot <shep-agent@users.noreply.github.com>

Copy link
Copy Markdown
Contributor Author

Unit Tests (windows-latest) failed again on 4d1b86f, this time with a different cause. vitest's esbuild transform service on the Windows runner died about 25 seconds into the job (13:33:31Z). After that, every test file that had not loaded yet failed with Error: The service is no longer running (vite:esbuild): 1,232 files failed to load, while all 2,827 tests that did run passed. No assertion failed.

No test in this PR's diff ran before the service died, and the terminal test fixed in 4d1b86f was among the files that never loaded. Before this run, Windows unit tests had completed with only one failure in total (the teardown fixed in 4d1b86f). Every other job on 4d1b86f is green.

This needs a re-run of Unit Tests (windows-latest). This integration gets a 403 when it tries to re-run workflows, so could someone with access re-run it? If esbuild dies again, it is reproducible and I will dig into what is taking the transform service down.


Generated by Claude Code

Copy link
Copy Markdown
Contributor Author

I looked into whether this PR causes the esbuild crash in Unit Tests (windows-latest) on 4d1b86f, and found no link to its diff.

  • When the service died. test:unit started at 13:33:06Z, and the first file reporting (0 test) appeared at 13:33:31Z. Before that point 68 test files had loaded and run.
  • The PR's own tests that ran before the crash all passed. Four of the 68 files are in this PR's diff:
    • settings.mapper.test.ts (132 tests)
    • agent-executor-factory.test.ts (62)
    • cloud-deploy-use-cases.test.ts (22)
    • settings-defaults.factory.test.ts (42)
  • The log shows nothing unusual before the crash. I checked from the job start to 13:33:31 for a crash, an out-of-memory error or an esbuild error, and found none. The last lines are normal βœ“ results (status.command.test.ts, ensure-infra.node.test.ts, start-daemon stdout). The only process killing nearby, in deployment.service.test.ts, goes through an injected deps.kill mock.
  • Nothing in the diff targets the test runner or kills processes. The PR does not change any vitest or vite config, pool setting, test setup file or package.json. None of its new modules spawn processes or do heavy work at import time.
  • The same code passed this job on earlier commits. Earlier Windows unit runs on this PR (b4e7420, 8c0e469, 9264d3c) loaded and ran all 1,301 files. The only code change since 9264d3c is the teardown in pty-terminal-session.service.test.ts, and that file never loaded in this run (it is among the (0 test) files).

I conclude that the esbuild service on the runner died in the middle of the run, and that nothing in this PR caused it. A re-run of Unit Tests (windows-latest) should confirm it: https://github.com/shep-ai/shep/actions/runs/37937489099. This integration cannot re-run it (403). If it fails the same way again, it is reproducible and I will chase it.


Generated by Claude Code

…up-cuts

Renumber the software-factory feature-flag migration from 166 to 169: main's 166 is
add-agent-question-decision, and #924 and telemetry hold 167 and 168. Take main's empty-file
git config isolation in the test harness and keep both sides' LESSONS entries.

Claude-Session: https://claude.ai/code/session_01TxtN2VcWzg6NTCLj4DbmfL

Co-Authored-By: Shep Bot <shep-agent@users.noreply.github.com>
arielshad added a commit that referenced this pull request Oct 11, 2026
## What

Adds `docs/competitors/t3code.md`, a product and architecture review of
T3 Code (pingdotgg/t3code) compared with Shep, and links it from
`docs/competitors/README.md`. The doc covers four topics:

- **What to adopt:** the inline-options decision pattern, plan capture,
hidden-ref checkpoints and capability records.
- **What to skip.**
- **What to cut or consolidate in Shep.**
- **A telemetry design.**

It ends with the owner's decision on every recommendation.

## Why

This is the record behind the follow-up work:

- spec 133 metrics (#923)
- spec 134 unified decisions (#922, #924)
- spec 135 cleanup (#925)
- the Effect-TS evaluation issue (#921)

## Screenshots / Recording

N/A β€” non-UI change.

## Testing

Docs only. Markdown is excluded from Prettier (`*.md` in
`.prettierignore`). I checked the claims in the doc against
`main@0304cfc` and `pingdotgg/t3code@a4c9494b`.

## Checklist

- [x] Commit messages follow [Conventional
Commits](https://www.conventionalcommits.org/) β€” `docs`, so no release

πŸ€– Generated with [Claude Code](https://claude.com/claude-code)

https://claude.ai/code/session_01SGhpsQ4T9PshbZFxzCQQMy

---
_Generated by [Claude
Code](https://claude.ai/code/session_01SGhpsQ4T9PshbZFxzCQQMy)_

---------

Co-authored-by: Claude <noreply@anthropic.com>
claude and others added 3 commits October 11, 2026 10:11
…up-cuts

Co-Authored-By: Shep Bot <shep-agent@users.noreply.github.com>
The spec waited for the first server-action response on /settings and then on
response.finished(). The page fires other actions on load, and a server action's
response streams the re-rendered page, so finished() hung for 60s in CI. Match
the save by its preferredLanguage body and rely on the reload check instead.

Claude-Session: https://claude.ai/code/session_01TxtN2VcWzg6NTCLj4DbmfL

Co-Authored-By: Shep Bot <shep-agent@users.noreply.github.com>
…up-cuts

Bring in telemetry (#923, migration 168); the feature-flag migration stays at 169.
Take main's settings-rows extraction and stories, keeping the row's flex basis so
switches stay beside long descriptions. Keep the i18n spec's save-matching without
response.finished(), which hung in CI.

Claude-Session: https://claude.ai/code/session_01TxtN2VcWzg6NTCLj4DbmfL

Co-Authored-By: Shep Bot <shep-agent@users.noreply.github.com>
arielshad pushed a commit that referenced this pull request Oct 11, 2026
135 belongs to the review-cleanup spec in #925.

Claude-Session: https://claude.ai/code/session_014Zwb23kb4xTBxJtqc2hKbU

Co-Authored-By: Shep Bot <shep-agent@users.noreply.github.com>
@arielshad
arielshad marked this pull request as ready for review October 11, 2026 11:35
@arielshad
arielshad merged commit 58df74f into main Oct 11, 2026
25 checks passed
@arielshad
arielshad deleted the feat/133-review-cleanup-cuts branch October 11, 2026 11:35
arielshad pushed a commit that referenced this pull request Oct 11, 2026
<p align="center">
  <a href="https://github.com/shep-ai/shep">
    <img src="https://raw.githubusercontent.com/shep-ai/shep/main/docs/screenshots/shep-card.jpg" alt="Shep β€” run multiple AI agents in parallel" width="720" />
  </a>
</p>

# πŸš€ Shep [v1.240.0](/compare/v1.239.0...v1.240.0) Β· _2026-10-11_

> Your organization does not have access to Claude. Please login again or contact your administrator.

### ✨ Features

* **web:** review cleanup β€” per-area feature flags, ASPM off by default ([#925](#925)) ([58df74f](58df74f)), closes [#923](#923) [922/#924](#924)

  ![notice light](https://raw.githubusercontent.com/shep-ai/shep/v1.240.0/specs/133-telemetry/evidence/notice-card-light.png)
  ![settings light](https://raw.githubusercontent.com/shep-ai/shep/v1.240.0/specs/133-telemetry/evidence/settings-section-light.png)
  ![notice dark](https://raw.githubusercontent.com/shep-ai/shep/v1.240.0/specs/133-telemetry/evidence/notice-card-dark.png)
  ![settings dark](https://raw.githubusercontent.com/shep-ai/shep/v1.240.0/specs/133-telemetry/evidence/settings-section-dark.png)

## πŸ“¦ Install or update

```bash
# upgrade an existing install
npm i -g @shepai/cli@1.240.0

# or run instantly without installing
npx @shepai/cli@latest
```

## πŸ’¬ Join the community

[πŸ’¬ **Discord**](https://discord.gg/ES6tdVFfur) Β· [πŸ“– **Docs**](https://github.com/shep-ai/shep#readme) Β· [⭐ **Star on GitHub**](https://github.com/shep-ai/shep) Β· [πŸ› **Report an issue**](https://github.com/shep-ai/shep/issues)

---

<sub>πŸ€– Released autonomously by Shep β€” built by parallel AI agents working in isolated git worktrees. Try it: `npx @shepai/cli`</sub>

Co-Authored-By: Shep Bot <shep-agent@users.noreply.github.com>
arielshad added a commit that referenced this pull request Oct 11, 2026
… (spec 136) (#929)

## What

This is the telemetry follow-up noted in #922 and #924. Every recorded
answer to an agent decision now emits `decision.answered`, and a
background agent's question that reaches its deadline emits the new
`decision.defaulted` event. Both go through the `ITelemetry` port from
#923.

| Event | Properties | Recorded by |
| ----- | ---------- | ----------- |
| `decision.answered` | `kind` (`DecisionKind`), `surface` (new
`DecisionSurface`: web / cli / chat / supervisor / other), `latency`
(bucket from asked to answered), `pickedRecommended` |
`AnswerAgentQuestionUseCase`, once per answer it actually records |
| `decision.defaulted` | `kind`, `timeout` (bucket of the deadline the
agent set) | `AskAgentDecisionUseCase`, when its deadline settle wins |

## Why

Spec 133 defined `decision.answered` but did not emit it, and its `kind`
and `surface` were raw strings. The T3 Code review asks three things
that need data: whether people answer agent questions, where they answer
them, and whether they take the recommendation. It also asks how often
agents proceed on their own at the deadline.

- **Emit points:** each event is emitted right after the use case's
atomic `settlePending` succeeds. Each decision is therefore counted
exactly once without an `onceKey`. A lost race (CLI vs web), a refused
answer, or agent questions being off records nothing. A person answering
at the deadline records an answer, not a default.
- **Surface:** each caller passes its own surface: the web action
(`web`, which overrides anything the client sends), the CLI (`cli`), the
chat bridge (`chat`) and the supervisor router (`supervisor`). Nothing
parses the free-form `answeredBy` actor.
- **Typing:** in the property map, `kind` and `surface` are now enums.
That makes the compiler reject a content property at every emit site.
- **Refactor while touching the file:** the `AskAgentDecisionOutcome`
string union is now the `DecisionOutcome` enum, with a new `Disabled`
member. The MCP tool's wire values are unchanged. Stored questions use
`RecordedDecisionOutcome`, which excludes `Disabled`, so the activity
log needs no UI for a state it can never show.
- **Docs:** `docs/telemetry.md` lists both events. Spec:
`specs/136-decision-telemetry/`. It was renumbered from 135, which
belongs to #925; the branch name still says 135.

## Screenshots / Recording

No UI changes.

## Testing

- `answer-agent-question.use-case.test.ts`:
- Properties for a decision answered on the web, including the latency
bucket with a faked clock and `pickedRecommended: true`.
  - Typed text counts as not the recommendation.
- A legacy question is reported as `Legacy`, with `Other` as the default
surface.
  - Answering twice records one event.
  - Agent questions off, or a refused answer, records nothing.
- `ask-agent-decision.use-case.test.ts`:
- The deadline default records `decision.defaulted` with the timeout
bucket.
  - A person answering at the deadline records no default.
  - Cancelled and disabled outcomes record no default.
- Caller tests assert the surface they pass: web action, CLI `answer`
(with `--answer` and interactive), chat bridge, supervisor router.

Ran locally: `pnpm lint`, `format:check`, `check:stories`, `typecheck`,
`test:unit` (14864 passed), `test:int` (2132 passed), `build`,
`build:web`, `build:storybook`, and `generate` with a clean diff. After
the renumbering: lint, format:check, typecheck and the affected test
files.

## Checklist

- [x] `pnpm lint` passes
- [x] `pnpm format:check` passes
- [x] `pnpm typecheck` passes
- [x] `pnpm test:unit` and `pnpm test:int` pass
- [x] `pnpm build` succeeds
- [x] (Domain changes) `pnpm tsp:compile` ran and
`packages/core/src/domain/generated/output.ts` is committed
- [x] Tests landed RED-first per the TDD guide
- [x] No `domain/` or `application/` file imports anything from
`infrastructure/`
- [x] Commit messages follow Conventional Commits

πŸ€– Generated with [Claude Code](https://claude.com/claude-code)

https://claude.ai/code/session_014Zwb23kb4xTBxJtqc2hKbU

---------

Co-authored-by: Claude <noreply@anthropic.com>
Co-authored-by: Shep Bot <shep-agent@users.noreply.github.com>
arielshad added a commit that referenced this pull request Oct 11, 2026
## What

`ITerminalSessionService.close()` now returns a promise that resolves
once the session's processes have exited. It kills the pty and waits for
the exit event, with a time limit. On Windows it then terminates the
shell and its ConPTY host (`conhost.exe` / `OpenConsole.exe`) by their
**captured PIDs** if either is still running. `DELETE /api/terminal/:id`
waits for this to finish.

- **New `ConptyHostReaper`**
(`infrastructure/services/terminal/conpty-host-reaper.ts`):
- When a terminal is created, it asks Windows for the shell's console
host (`NtQueryInformationProcess(ProcessConsoleHostProcess)`). The query
has to run while the shell is alive.
- Before killing anything, it checks with `tasklist` that the PID still
has the image it had when captured. A recycled PID is never killed, and
no process is ever matched by name.
- **Host reaped on natural exit too:** when the shell exits by itself
(`exit`), the host is also terminated if it survives.
- **cwd validation moved:** it now lives in `terminal-cwd.ts`, which
keeps the service at about 300 lines.

## Why

Closes #927.

I probed node-pty 1.1.0 on windows-2022 (two CI rounds on this PR, using
a temporary Windows-only probe that is now deleted):

| Case | Result |
| --- | --- |
| Closed right after `create()` | `cmd.exe` and `conhost.exe` both
stayed alive for about 0.7 s. The cwd was `EBUSY` that whole time. The
exit event arrived after about 650 ms. |
| Closed once the shell was ready | Both processes exited within about
10 ms. |
| `useConptyDll: true` | `OpenConsole.exe` was still running seconds
after the exit event. |

The host's parent is our own process, not the shell, so waiting for the
shell alone does not cover it.

**node-pty upgrade:** not done. 1.1.0 is still the latest stable release
and already has `useConptyDll`, which made the leak worse in the probe.
The 1.2.0 releases are all betas.

## Screenshots / Recording

N/A β€” non-UI change.

## Testing

**New tests:**
- **Windows only:** after `await close()`, no process the session
started is still alive, and its cwd can be removed with no retries. This
runs once closing right after create and once after the shell is ready.
- **All platforms:** `close()` resolves only after the shell's exit
listeners have fired.
- **All platforms:** closing an unknown session is a no-op.
- **`ConptyHostReaper`:** output parsing; never kills a PID whose image
changed; handles a process that already exited; terminates a survivor
(Windows).

The first-round RED test passed on Windows because an immediate `rmSync`
is a timing-dependent signal. That is why the assertion is now on the
processes themselves; LESSONS.md explains this. The all-platform close
test failed before the fix on Linux.

**Local runs on the merged head:**

| Check | Result |
| --- | --- |
| `pnpm lint`, `pnpm format:check` | pass |
| `pnpm typecheck`, `pnpm typecheck:web` | pass |
| `pnpm test:unit` | 14,935 passed |
| `pnpm test:int` | 2,139 passed |
| `pnpm build` | pass |

The Windows behaviour is verified only by the `Unit Tests
(windows-latest)` job.

## Notes for the reviewer

- **#925 workaround removed:** the `removeShellCwd` EBUSY workaround
from #925 is gone. The test now awaits `close()` and removes the
directory normally.
- **Cost per terminal on Windows:** the host capture launches one
PowerShell process per terminal opened, and compiling the P/Invoke shim
takes about 2.5 s. It runs in the background. `close()` waits for it
only when a terminal is closed within those first seconds.

## Checklist

- [x] `pnpm lint` passes
- [x] `pnpm format:check` passes
- [x] `pnpm typecheck` passes
- [x] `pnpm test:unit` and `pnpm test:int` pass
- [x] `pnpm build` succeeds
- [x] No `domain/` or `application/` file imports from `infrastructure/`
- [x] Conventional Commits; `fix` for the user-visible change
- [x] Updated LESSONS.md

πŸ€– Generated with [Claude Code](https://claude.com/claude-code)

https://claude.ai/code/session_01Kqkw9jhaERENoT56mDS7ZR

---------

Co-authored-by: Claude <noreply@anthropic.com>
Co-authored-by: Shep Bot <shep-agent@users.noreply.github.com>
arielshad added a commit that referenced this pull request Oct 11, 2026
…932)

## What

Adds an `sdlcBoard` feature flag, **off by default**, and puts the SDLC
board behind it. While the flag is off:

- the sidebar hides the **SDLC Board** link;
- `/sdlc` returns 404;
- `GET /api/sdlc-events` returns 404;
- `listSdlcBoard`, `reorderSdlcTask`, `updateSdlcTaskStatus` and
`updateSdlcSubTaskStatus` return an error without calling their use
cases.

The flag is in the feature-flag catalog under **Platform**. It is listed
on `/settings/feature-flags` with a switch, and `shep settings flags
enable sdlcBoard` turns it on from the CLI. While it is on, everything
works as before.

Unchanged: the Control Center canvas, the `/features` table, and the
agent-side board tracker. The tracker keeps recording tasks, so turning
the flag on shows a complete board. The board has no CLI command group,
so there is no CLI gate to add.

| Commit | Change |
| --- | --- |
| `19edf55` | The flag: TypeSpec (`= false`), defaults, migration
**171** (`feature_flag_sdlc_board INTEGER NOT NULL DEFAULT 0`), mapper,
repository SQL, web flag state, catalog entry, and labels in all 9
locales |
| `777699b` | One shared web feature gate in `lib/feature-gate.ts`
(`isFeatureOn`, `featureNotFound`, `FEATURE_OFF_ERROR`).
`requireFeaturePage` and the feedback/alerts routes now use it;
`intakeDisabled()` is removed |
| `6227dca` | Gates the sidebar link, the page, the SSE route and the
four actions |
| `50e73f2` | The `/sdlc` accessibility e2e case turns the flag on;
FEATURES, domain-models and the LESSONS flag checklist are updated |
| `05e4558` | Migration renumbered from 170 to 171 (170 is the
checkpoints workstream's) |

## Why

The owner decided the SDLC board should not be part of the default
product surface. Implements `specs/140-sdlc-board-flag/`, building on
the flag catalog and `/settings/feature-flags` from #925 (spec 135).

## Screenshots / Recording

There is no new component. The flags view renders the new row from the
catalog, like every other flag. The web e2e suite ran locally against
the release build; it covers `/settings/feature-flags`, plus `/sdlc`
with the flag on.

## Testing

Local runs:

| Check | Result |
| --- | --- |
| `pnpm generate` | no drift |
| `pnpm lint`, `pnpm format:check`, `pnpm typecheck`, `pnpm tsp:compile`
| pass |
| `pnpm test:unit` | 14,953 passed (on the merge with `main` at
`1e1f859`) |
| `pnpm test:int` | 2,142 passed |
| `pnpm check:stories` | pass |
| `pnpm build:release`, `pnpm build:storybook` | pass |
| Web e2e with CI settings (flaky counts as failure) | 86 of 86 passed |

New tests, each landed RED first:

- `tests/unit/presentation/web/app/sdlc-board-gating.test.ts` covers the
page, the SSE route and all four actions, with the flag off and on.
- `tests/unit/presentation/web/lib/feature-gate.test.ts`.
- A sidebar-links case for `/sdlc`.
- `migration-171-sdlc-board-flag.test.ts`, which checks the column,
`DEFAULT 0` and idempotency.
- A settings repository round-trip.
- The mapper both ways, the catalog group and the defaults.

## Checklist

- [x] `pnpm lint` passes
- [x] `pnpm format:check` passes
- [x] `pnpm typecheck` passes
- [x] `pnpm test:unit` and `pnpm test:int` pass
- [x] `pnpm build` succeeds
- [x] `pnpm build:storybook` succeeds (no new components)
- [x] `pnpm tsp:compile` ran and
`packages/core/src/domain/generated/output.ts` is committed
- [x] Tests landed RED-first
- [x] No `domain/` or `application/` file imports anything from
`infrastructure/`
- [x] Conventional Commits; `feat` for the user-visible change
- [x] LESSONS.md: the flag checklist now names the shared gate, and says
that API routes and server actions need the gate too, not just the page

πŸ€– Generated with [Claude Code](https://claude.com/claude-code)

https://claude.ai/code/session_01TxtN2VcWzg6NTCLj4DbmfL

---
_Generated by [Claude
Code](https://claude.ai/code/session_01TxtN2VcWzg6NTCLj4DbmfL)_

---------

Co-authored-by: Claude <noreply@anthropic.com>
Co-authored-by: Shep Bot <shep-agent@users.noreply.github.com>
arielshad added a commit that referenced this pull request Oct 11, 2026
#931)

## What

After every feature-agent step that can change files, Shep now snapshots
the feature's worktree into a hidden ref,
`refs/shep/checkpoints/<featureId>/<seq>-<node>`, and records it in
SQLite. Users can then:
- see what each step changed,
- diff any two steps,
- roll the worktree back to a step, from the CLI or from a new
**Checkpoints** tab in the feature drawer.

## Why

Implements `specs/138-git-checkpoints/`: workstream T4 of the T3 Code
review (`docs/competitors/t3code.md`, "What Shep Should Take" Β§3).

Until now a run kept only its end state, the branch. Nobody could answer
"what did *implement* change, and what did *repair* change after it?",
or undo one bad step without editing git by hand.

**Capture**

Capture follows T3's approach and never touches the user's branch, HEAD,
index or files:
1. Seed a temporary `GIT_INDEX_FILE`.
2. `add -A`, then `write-tree`.
3. `commit-tree` with a fixed identity and no signing, then
`update-ref`.

All writes run with `core.fsync=objects,reference`.
- **Where it hooks in.** A `withCheckpoint` wrapper covers every
file-changing node of the full, fast and exploration graphs.
- **Baseline.** A baseline is captured before the first step.
- **Unchanged steps.** A step that changed nothing records nothing.
- **Failures.** A capture failure is logged and never fails the run.

**Diff**

Diffs run `git diff` between two checkpoint commits and are cached
in-process. The unified-diff parser previously in `GitPrService` (with a
hand copy in the GitHub review service) is now one shared module.

**Restore**

Restore first captures a `pre-restore` checkpoint, so a restore can
itself be undone. It then runs `clean -fd`, `read-tree --reset -u` and
`reset -q`. The working tree matches the checkpoint, ignored files are
kept, and the branch is not moved, so nothing already pushed is
rewritten.

A single `CheckpointRestorePolicy` gates restore, and the list exposes
its answer as a typed `CheckpointRestoreBlocker`. It refuses unless all
three hold:
- the feature has its own worktree, distinct from the repository path;
- git confirms the directory is a linked worktree;
- the latest agent run is not pending or running.

**Cleanup**

Refs and rows are deleted when the worktree is removed (post-merge
cleanup, delete) or the feature is archived (manual or auto-archive).

**Data model**

- New TypeSpec entity `GitCheckpoint`.
- Enums `GitCheckpointKind` and `CheckpointRestoreBlocker`.
- Migration **170** `git_checkpoints`. 169 belongs to #925, which is
merged into this branch.

## Screenshots / Recording

Storybook (`Features/Checkpoints/*`) with fixture data. Light and dark.

| | Light | Dark |
| --- | --- | --- |
| Per-step diff |
![](https://github.com/shep-ai/shep/blob/feat/138-git-checkpoints/specs/138-git-checkpoints/evidence/checkpoints-tab-light.png?raw=true)
|
![](https://github.com/shep-ai/shep/blob/feat/138-git-checkpoints/specs/138-git-checkpoints/evidence/checkpoints-tab-dark.png?raw=true)
|
| Compare two steps |
![](https://github.com/shep-ai/shep/blob/feat/138-git-checkpoints/specs/138-git-checkpoints/evidence/checkpoints-tab-compare-light.png?raw=true)
|
![](https://github.com/shep-ai/shep/blob/feat/138-git-checkpoints/specs/138-git-checkpoints/evidence/checkpoints-tab-compare-dark.png?raw=true)
|
| Restore blocked (agent running) |
![](https://github.com/shep-ai/shep/blob/feat/138-git-checkpoints/specs/138-git-checkpoints/evidence/checkpoint-list-restore-disabled-light.png?raw=true)
|
![](https://github.com/shep-ai/shep/blob/feat/138-git-checkpoints/specs/138-git-checkpoints/evidence/checkpoint-list-restore-disabled-dark.png?raw=true)
|
| Restore confirmation |
![](https://github.com/shep-ai/shep/blob/feat/138-git-checkpoints/specs/138-git-checkpoints/evidence/restore-dialog-light.png?raw=true)
|
![](https://github.com/shep-ai/shep/blob/feat/138-git-checkpoints/specs/138-git-checkpoints/evidence/restore-dialog-dark.png?raw=true)
|

## Testing

**Real git** (global/system config isolated, signing off, worktree path
with a space):
`tests/integration/infrastructure/services/git/git-checkpoint.service.test.ts`
- Capture leaves `git status`, HEAD, branch and index unchanged.
- Untracked files are captured; ignored files are not.
- The ref is visible from the main checkout.
- Diff and stat work, and the cache returns the same object.
- Restore equals the checkpoint, keeps ignored files and leaves the
branch alone.
- Linked worktree vs main checkout is detected.
- Ref deletion removes only the feature's refs.
- The unborn-branch case works.
- A directory that is not the top of its own work tree is refused, e.g.
a leftover worktree directory inside another repository after post-merge
cleanup, so the parent checkout is never snapshotted.
- A mutation check passed: without `GIT_INDEX_FILE` the "index
untouched" test goes red.

**Repository:**
`tests/integration/infrastructure/repositories/sqlite-git-checkpoint.repository.test.ts`
(non-default round-trip, UNIQUE per feature, delete, idempotent
migration).

**Use cases** (`tests/unit/application/use-cases/features/checkpoints/`)
- Baseline and step numbering; unchanged tree skipped.
- Per-step and two-step diffs.
- Every restore refusal; Windows-spelled repository path treated as the
same checkout.
- Best-effort delete.

**Pipeline:** `with-checkpoint.test.ts` and
`checkpoint-capture.test.ts`, plus graph-level tests asserting which
nodes capture in the full, fast and exploration graphs (validate nodes
never capture).

**Cleanup hooks:** archive, auto-archive watcher, worktree cleanup,
delete.

**CLI:** `checkpoints.command.test.ts`.

**Web:** `checkpoints-tab.test.tsx` (per-step, baseline, compare,
restore confirm/refusal, blocker, empty, error), plus drawer tab
visibility.

**DI:** the bootstrap test resolves every new token and goes red without
the registration.

**Storybook:** `CheckpointList` (Default / RestoreDisabled /
WithPreRestore / WithoutRestore), `CheckpointCompareBar`,
`CheckpointRestoreDialog` (Default / Pending / Closed),
`CheckpointsTab`.

Local results, on the tree merged with `main`:
- `lint`, `format:check`, `typecheck`, `check:stories` and `generate`
(no drift) all pass.
- `test:unit`: 15,043 passed.
- `test:int`: 2,160 passed. The first run had 7 failures in
`dependency-path-traversal`, caused by the unpatched `extract-zip` I had
installed into the sandbox as a stand-in; with the repo patch applied,
all pass.
- `build`, `build:web` and `build:storybook` pass.

## Checklist

- [x] `pnpm lint` passes
- [x] `pnpm format:check` passes
- [x] `pnpm typecheck` passes
- [x] `pnpm test:unit` and `pnpm test:int` pass
- [x] `pnpm build` succeeds
- [x] (UI only) `pnpm build:storybook` and `pnpm build:web` succeed;
every new component has a colocated `.stories.tsx`
- [x] (Domain changes) `pnpm generate` ran and `output.ts` plus
`apis/json-schema/` are committed
- [x] (New use case) Tests landed RED-first per the TDD guide
- [x] No `domain/` or `application/` file imports anything from
`infrastructure/`
- [x] Commit messages follow Conventional Commits
- [x] Updated LESSONS.md

πŸ€– Generated with [Claude Code](https://claude.com/claude-code)

https://claude.ai/code/session_01V37fpj6cexvgoExiG9SHKj

---
_Generated by [Claude
Code](https://claude.ai/code/session_01V37fpj6cexvgoExiG9SHKj)_

---------

Co-authored-by: Claude <noreply@anthropic.com>
Co-authored-by: Shep Bot <shep-agent@users.noreply.github.com>
arielshad pushed a commit that referenced this pull request Oct 11, 2026
<p align="center">
  <a href="https://github.com/shep-ai/shep">
    <img src="https://raw.githubusercontent.com/shep-ai/shep/main/docs/screenshots/shep-card.jpg" alt="Shep β€” run multiple AI agents in parallel" width="720" />
  </a>
</p>

# πŸš€ Shep [v1.241.0](/compare/v1.240.0...v1.241.0) Β· _2026-10-11_

> Your organization does not have access to Claude. Please login again or contact your administrator.

### ✨ Features

* **agents:** emit decision.answered and decision.defaulted telemetry (spec 136) ([#929](#929)) ([1e1f859](1e1f859)), closes [#922](#922) [#925](#925)

  ![default](https://raw.githubusercontent.com/shep-ai/shep/v1.241.0/specs/134-unified-decisions/evidence/decision-panel-default.png)
  ![dark](https://raw.githubusercontent.com/shep-ai/shep/v1.241.0/specs/134-unified-decisions/evidence/decision-panel-dark.png)
  ![gate](https://raw.githubusercontent.com/shep-ai/shep/v1.241.0/specs/134-unified-decisions/evidence/decision-panel-approval-gate.png)
  ![not resumable](https://raw.githubusercontent.com/shep-ai/shep/v1.241.0/specs/134-unified-decisions/evidence/decision-panel-not-resumable.png)
* **agents:** git checkpoints after each feature-agent step (spec 138) ([#931](#931)) ([26265ed](26265ed)), closes [#925](#925)

  ![checkpoints tab light](https://raw.githubusercontent.com/shep-ai/shep/v1.241.0/specs/138-git-checkpoints/evidence/checkpoints-tab-light.png)
  ![checkpoints tab dark](https://raw.githubusercontent.com/shep-ai/shep/v1.241.0/specs/138-git-checkpoints/evidence/checkpoints-tab-dark.png)
  ![checkpoints tab compare light](https://raw.githubusercontent.com/shep-ai/shep/v1.241.0/specs/138-git-checkpoints/evidence/checkpoints-tab-compare-light.png)
  ![checkpoints tab compare dark](https://raw.githubusercontent.com/shep-ai/shep/v1.241.0/specs/138-git-checkpoints/evidence/checkpoints-tab-compare-dark.png)
* **web:** put the SDLC board behind a feature flag, off by default ([#932](#932)) ([e71f138](e71f138)), closes [#925](#925)

### πŸ› Bug Fixes

* **cli:** wait for the conpty host to exit when a terminal closes ([#928](#928)) ([94a846d](94a846d)), closes [#925](#925) [#925](#925)

## πŸ“¦ Install or update

```bash
# upgrade an existing install
npm i -g @shepai/cli@1.241.0

# or run instantly without installing
npx @shepai/cli@latest
```

## πŸ’¬ Join the community

[πŸ’¬ **Discord**](https://discord.gg/ES6tdVFfur) Β· [πŸ“– **Docs**](https://github.com/shep-ai/shep#readme) Β· [⭐ **Star on GitHub**](https://github.com/shep-ai/shep) Β· [πŸ› **Report an issue**](https://github.com/shep-ai/shep/issues)

---

<sub>πŸ€– Released autonomously by Shep β€” built by parallel AI agents working in isolated git worktrees. Try it: `npx @shepai/cli`</sub>

Co-Authored-By: Shep Bot <shep-agent@users.noreply.github.com>
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

2 participants